﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using System.Web.UI;
using System.Web.UI.WebControls;

#if NOT_LEGACY
using System.Web.Script.Serialization;
#endif

namespace Awesome.ClientID
{
    public class PageSerializer
    {
        private readonly Page _page;
        private readonly StringBuilder _output;

        private readonly JavaScriptSerializer _serializer = new JavaScriptSerializer();

        private readonly IDictionary<string, object> _controlsDictionary;
        private readonly IDictionary<string, object> _propertiesDictionary;

        private readonly BindingFlags _flags = BindingFlags.Public | BindingFlags.NonPublic 
                                             | BindingFlags.Static | BindingFlags.Instance
                                             | BindingFlags.FlattenHierarchy;

        private static readonly IList<Type> ValidControls = new List<Type>
                                                                {
                                                                    typeof (TextBox),
                                                                    typeof (CheckBox),
                                                                    typeof (DropDownList),
                                                                    typeof (HiddenField),
                                                                    typeof (Button)
                                                                };

        private static readonly IList<Type> ValidContainers = new List<Type>
                                                                  {
                                                                      typeof (MasterPage),
                                                                      typeof (Page),
                                                                      typeof (UserControl),
                                                                      typeof (ContentPlaceHolder)
                                                                  };

        public PageSerializer(Page page)
        {
            _page = page;
            _output = new StringBuilder();
            _propertiesDictionary = new Dictionary<string, object>();
            _controlsDictionary = new Dictionary<string, object>();
        }

        public void Serialize()
        {
            FindAndSerializeProperties(_page, _page.GetType());
            IterateControls(_page.Controls);

            _output.Append("var properties = ");
            _output.Append(_serializer.Serialize(_propertiesDictionary));
            _output.Append(";");

            _output.AppendLine("");

            _output.Append("var controls = ");
            _output.Append(_serializer.Serialize(_controlsDictionary));
            _output.Append(";");

            _page.ClientScript.RegisterClientScriptBlock(_page.GetType(), "PageDataSerialization", _output.ToString(), true);
        }

        private void IterateControls(ControlCollection controlCollection)
        {
            foreach (Control control in controlCollection)
            {
                var type = control.GetType();
                var isContainerType = CheckIsContainerType(type);
                var isValidControlType = ValidControls.Contains(control.GetType());

                if (isContainerType)
                {
                    FindAndSerializeProperties(control, type);
                }

                if (isValidControlType)
                {
                    _controlsDictionary.Add(control.ID, control.ClientID);
                }

                if (control.Controls.Count != 0)
                {
                    IterateControls(control.Controls);
                }
            }
        }

        private void FindAndSerializeProperties(Control control, Type type)
        {
            var properties = type.GetProperties(_flags);

            foreach (var propertyInfo in properties)
            {
                var result = propertyInfo.GetCustomAttributes(typeof(CanSerializePropertyAttribute), true) as CanSerializePropertyAttribute[];

                if (result != null && result.Length == 1) // Should only have 1 result
                {
                    var attribute = result[0];
                    string name;

                    if (attribute.PrefixWithContainerId)
                        name = control.ID + propertyInfo.Name;
                    else
                        name = propertyInfo.Name;

                    _propertiesDictionary.Add(name, propertyInfo.GetValue(control, null));
                }
            }
        }

        private static bool CheckIsContainerType(Type type)
        {
            bool found = ValidContainers.Contains(type);

            if (!found)
            {
                var baseType = type.BaseType;

                while (true)
                {
                    if (ValidContainers.Contains(baseType))
                    {
                        found = true;
                        break;
                    }

                    if (baseType.BaseType == null)
                    {
                        break;
                    }

                    baseType = baseType.BaseType;
                }
            }
            return found;
        }
    }
}
